package com.kexio.file.util;

import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 文件哈希计算工具类
 * 
 * 提供文件内容的哈希值计算功能，支持：
 * - MD5 哈希计算
 * - SHA256 哈希计算
 * - 流式计算（适合大文件）
 * 
 * @author Kexio Team
 * @since 1.0.0
 */
public class FileHashUtils {
    
    private static final Logger log = LoggerFactory.getLogger(FileHashUtils.class);
    
    private static final String MD5_ALGORITHM = "MD5";
    private static final String SHA256_ALGORITHM = "SHA-256";
    
    /**
     * 文件哈希结果
     */
    public static class FileHashResult {
        private final String md5;
        private final String sha256;
        private final long fileSize;
        
        public FileHashResult(String md5, String sha256, long fileSize) {
            this.md5 = md5;
            this.sha256 = sha256;
            this.fileSize = fileSize;
        }
        
        public String getMd5() { return md5; }
        public String getSha256() { return sha256; }
        public long getFileSize() { return fileSize; }
        
        @Override
        public String toString() {
            return String.format("FileHashResult{md5='%s', sha256='%s', size=%d}", md5, sha256, fileSize);
        }
    }
    
    /**
     * 计算输入流的MD5和SHA256哈希值
     * 
     * @param inputStream 输入流
     * @return 哈希结果
     * @throws IOException IO异常
     */
    public static FileHashResult calculateHash(InputStream inputStream) throws IOException {
        try {
            MessageDigest md5Digest = MessageDigest.getInstance(MD5_ALGORITHM);
            MessageDigest sha256Digest = MessageDigest.getInstance(SHA256_ALGORITHM);
            
            // 使用DigestInputStream同时计算两个哈希值
            DigestInputStream md5Stream = new DigestInputStream(inputStream, md5Digest);
            DigestInputStream sha256Stream = new DigestInputStream(md5Stream, sha256Digest);
            
            byte[] buffer = new byte[8192];
            long totalBytes = 0;
            int bytesRead;
            
            while ((bytesRead = sha256Stream.read(buffer)) != -1) {
                totalBytes += bytesRead;
            }
            
            String md5Hash = bytesToHex(md5Digest.digest());
            String sha256Hash = bytesToHex(sha256Digest.digest());
            
            return new FileHashResult(md5Hash, sha256Hash, totalBytes);
            
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("哈希算法不支持", e);
        }
    }
    
    /**
     * 计算输入流的MD5哈希值
     * 
     * @param inputStream 输入流
     * @return MD5哈希值
     * @throws IOException IO异常
     */
    public static String calculateMd5(InputStream inputStream) throws IOException {
        try {
            MessageDigest digest = MessageDigest.getInstance(MD5_ALGORITHM);
            DigestInputStream digestStream = new DigestInputStream(inputStream, digest);
            
            byte[] buffer = new byte[8192];
            while (digestStream.read(buffer) != -1) {
                // 读取数据以更新摘要
            }
            
            return bytesToHex(digest.digest());
            
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5算法不支持", e);
        }
    }
    
    /**
     * 计算输入流的SHA256哈希值
     * 
     * @param inputStream 输入流
     * @return SHA256哈希值
     * @throws IOException IO异常
     */
    public static String calculateSha256(InputStream inputStream) throws IOException {
        try {
            MessageDigest digest = MessageDigest.getInstance(SHA256_ALGORITHM);
            DigestInputStream digestStream = new DigestInputStream(inputStream, digest);
            
            byte[] buffer = new byte[8192];
            while (digestStream.read(buffer) != -1) {
                // 读取数据以更新摘要
            }
            
            return bytesToHex(digest.digest());
            
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA256算法不支持", e);
        }
    }
    
    /**
     * 验证文件哈希值
     * 
     * @param inputStream 输入流
     * @param expectedMd5 期望的MD5值（可选）
     * @param expectedSha256 期望的SHA256值（可选）
     * @return 是否验证通过
     * @throws IOException IO异常
     */
    public static boolean verifyHash(InputStream inputStream, String expectedMd5, String expectedSha256) 
            throws IOException {
        FileHashResult result = calculateHash(inputStream);
        
        boolean md5Match = expectedMd5 == null || expectedMd5.equalsIgnoreCase(result.getMd5());
        boolean sha256Match = expectedSha256 == null || expectedSha256.equalsIgnoreCase(result.getSha256());
        
        return md5Match && sha256Match;
    }
    
    /**
     * 字节数组转十六进制字符串
     * 
     * @param bytes 字节数组
     * @return 十六进制字符串
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }
    
    /**
     * 十六进制字符串转字节数组
     * 
     * @param hex 十六进制字符串
     * @return 字节数组
     */
    public static byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                                + Character.digit(hex.charAt(i + 1), 16));
        }
        return data;
    }
    
    /**
     * 生成随机文件名
     * 
     * @param originalName 原始文件名
     * @param contentHash 内容哈希值
     * @return 新文件名
     */
    public static String generateStorageKey(String originalName, String contentHash) {
        String extension = "";
        if (originalName != null && originalName.contains(".")) {
            extension = originalName.substring(originalName.lastIndexOf("."));
        }
        
        // 使用哈希值的前16位作为文件名，避免重复
        String shortHash = contentHash.length() > 16 ? contentHash.substring(0, 16) : contentHash;
        
        // 按照哈希值分目录存储，避免单个目录文件过多
        String dir1 = shortHash.substring(0, 2);
        String dir2 = shortHash.substring(2, 4);
        
        return String.format("%s/%s/%s%s", dir1, dir2, contentHash, extension);
    }
}
